{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "81a6f12e",
   "metadata": {},
   "source": [
    "下面几段代码展示朴素贝叶斯模型的训练和预测。这里使用的数据集为本书自制的Books数据集，包含约1万本图书的标题，分为3种主题。首先是预处理，针对文本分类的预处理主要包含以下步骤：\n",
    "\n",
    "- 通常可以将英文文本全部转换为小写，或者将中文内容全部转换为简体，等等，这一般不会改变文本内容。\n",
    "- 去除标点。英文中的标点符号和单词之间没有空格（如——“Hi, there!”），如果不去除标点，“Hi,”和“there!”会被识别为不同于“Hi”和“there”的两个词，这显然是不合理的。对于中文，移除标点一般也不会影响文本的内容。\n",
    "- 分词。中文汉字之间没有空格分隔，中文分词有时比英文分词更加困难，此处不再赘述。\n",
    "- 去除停用词（如“I”、“is”、“的”等）。这些词往往大量出现但没有具体含义。\n",
    "- 建立词表。通常会忽略语料库中频率非常低的词。\n",
    "- 将词转换为词表索引（ID），便于机器学习模型使用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "27cad9a7-ab9d-4503-9603-f18f3c7cb0d8",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Promgfile\\Anaconda\\envs\\pytorch\\lib\\site-packages\\spacy\\util.py:894: UserWarning: [W095] Model 'zh_core_web_sm' (3.8.0) was trained with spaCy v3.8.0 and may not be 100% compatible with the current version (4.0.0.dev2). If you see errors or degraded performance, download a newer compatible model or retrain your custom model with the current spaCy version. For more details and available updates, run: python -m spacy validate\n",
      "  warnings.warn(warn_msg)\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "import os\n",
    "import requests\n",
    "import re\n",
    "from tqdm import tqdm\n",
    "from collections import defaultdict\n",
    "from string import punctuation\n",
    "import spacy\n",
    "from spacy.lang.zh.stop_words import STOP_WORDS\n",
    "nlp = spacy.load('zh_core_web_sm')\n",
    "# python -m spacy download zh_core_web_sm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "5936ceb0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train size = 8627 , test size = 2157\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|█████████████████████████████████████████████████████████████████████████████| 8627/8627 [00:28<00:00, 308.08it/s]\n",
      "100%|█████████████████████████████████████████████████████████████████████████████| 2157/2157 [00:07<00:00, 307.49it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['python', '编程', '入门', '教程']\n",
      "{'计算机类': 0, '艺术传媒类': 1, '经管类': 2}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "class BooksDataset:\n",
    "    def __init__(self):\n",
    "        train_file, test_file = './data/train.jsonl', './data/test.jsonl'\n",
    "\n",
    "        # 下载数据为JSON格式，转化为Python对象\n",
    "        def read_file(file_name):\n",
    "            with open(file_name, 'r', encoding='utf-8') as fin:\n",
    "                json_list = list(fin)\n",
    "            data_split = []\n",
    "            for json_str in json_list:\n",
    "                data_split.append(json.loads(json_str))\n",
    "            return data_split\n",
    "\n",
    "        self.train_data, self.test_data = read_file(train_file),\\\n",
    "            read_file(test_file)\n",
    "        print('train size =', len(self.train_data), \n",
    "              ', test size =', len(self.test_data))\n",
    "        \n",
    "        # 建立文本标签和数字标签的映射\n",
    "        self.label2id, self.id2label = {}, {}\n",
    "        for data_split in [self.train_data, self.test_data]:\n",
    "            for data in data_split:\n",
    "                txt = data['class']\n",
    "                if txt not in self.label2id:\n",
    "                    idx = len(self.label2id)\n",
    "                    self.label2id[txt] = idx\n",
    "                    self.id2label[idx] = txt\n",
    "                label_id = self.label2id[txt]\n",
    "                data['label'] = label_id\n",
    "\n",
    "    def tokenize(self, attr='book'):\n",
    "        # 使用以下两行命令安装spacy用于中文分词\n",
    "        # pip install -U spacy\n",
    "        # python -m spacy download zh_core_web_sm\n",
    "        # 去除文本中的符号和停用词\n",
    "        for data_split in [self.train_data, self.test_data]:\n",
    "            for data in tqdm(data_split):\n",
    "                # 转为小写\n",
    "                text = data[attr].lower()\n",
    "                # 符号替换为空\n",
    "                tokens = [t.text for t in nlp(text) \\\n",
    "                    if t.text not in STOP_WORDS]\n",
    "                # 这一步比较耗时，因此把tokenize的结果储存起来\n",
    "                data['tokens'] = tokens\n",
    "\n",
    "    # 根据分词结果建立词表，忽略部分低频词，\n",
    "    # 可以设置词最短长度和词表最大大小\n",
    "    def build_vocab(self, min_freq=3, min_len=2, max_size=None):\n",
    "        frequency = defaultdict(int)\n",
    "        for data in self.train_data:\n",
    "            tokens = data['tokens']\n",
    "            for token in tokens:\n",
    "                frequency[token] += 1 \n",
    "\n",
    "        print(f'unique tokens = {len(frequency)}, '+\\\n",
    "              f'total counts = {sum(frequency.values())}, '+\\\n",
    "              f'max freq = {max(frequency.values())}, '+\\\n",
    "              f'min freq = {min(frequency.values())}')    \n",
    "\n",
    "        self.token2id = {}\n",
    "        self.id2token = {}\n",
    "        total_count = 0\n",
    "        for token, freq in sorted(frequency.items(),\\\n",
    "            key=lambda x: -x[1]):\n",
    "            if max_size and len(self.token2id) >= max_size:\n",
    "                break\n",
    "            if freq > min_freq:\n",
    "                if (min_len is None) or (min_len and \\\n",
    "                    len(token) >= min_len):\n",
    "                    self.token2id[token] = len(self.token2id)\n",
    "                    self.id2token[len(self.id2token)] = token\n",
    "                    total_count += freq\n",
    "            else:\n",
    "                break\n",
    "        print(f'min_freq = {min_freq}, min_len = {min_len}, '+\\\n",
    "              f'max_size = {max_size}, '\n",
    "              f'remaining tokens = {len(self.token2id)}, '\n",
    "              f'in-vocab rate = {total_count / sum(frequency.values())}')\n",
    "\n",
    "    # 将分词后的结果转化为数字索引\n",
    "    def convert_tokens_to_ids(self):\n",
    "        for data_split in [self.train_data, self.test_data]:\n",
    "            for data in data_split:\n",
    "                data['token_ids'] = []\n",
    "                for token in data['tokens']:\n",
    "                    if token in self.token2id:\n",
    "                        data['token_ids'].append(self.token2id[token])\n",
    "\n",
    "        \n",
    "dataset = BooksDataset()\n",
    "dataset.tokenize()\n",
    "print(dataset.train_data[0]['tokens'])\n",
    "print(dataset.label2id)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26d79e05",
   "metadata": {},
   "source": [
    "完成分词后，对出现次数超过3次的词元建立词表，并将分词后的文档转化为词元id的序列。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "b3b4c04f-99e6-4b04-91c9-7603ea5f7100",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['D:\\\\Promgfile\\\\Anaconda\\\\envs\\\\pytorch', 'D:\\\\Promgfile\\\\Anaconda\\\\envs\\\\pytorch\\\\lib\\\\site-packages']\n"
     ]
    }
   ],
   "source": [
    "import site\n",
    "print(site.getsitepackages())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0d6b1918",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "unique tokens = 6956, total counts = 54884, max freq = 1635, min freq = 1\n",
      "min_freq = 3, min_len = 2, max_size = None, remaining tokens = 1650, in-vocab rate = 0.7944209605713869\n",
      "[18, 26, 5, 0]\n"
     ]
    }
   ],
   "source": [
    "dataset.build_vocab(min_freq=3)\n",
    "dataset.convert_tokens_to_ids()\n",
    "print(dataset.train_data[0]['token_ids'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d096d95f",
   "metadata": {},
   "source": [
    "接下来将数据和标签准备成便于训练的矩阵格式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "ba632265",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "train_X, train_Y = [], []\n",
    "test_X, test_Y = [], []\n",
    "\n",
    "for data in dataset.train_data:\n",
    "    x = np.zeros(len(dataset.token2id), dtype=np.int32)\n",
    "    for token_id in data['token_ids']:\n",
    "        x[token_id] += 1\n",
    "    train_X.append(x)\n",
    "    train_Y.append(data['label'])\n",
    "for data in dataset.test_data:\n",
    "    x = np.zeros(len(dataset.token2id), dtype=np.int32)\n",
    "    for token_id in data['token_ids']:\n",
    "        x[token_id] += 1\n",
    "    test_X.append(x)\n",
    "    test_Y.append(data['label'])\n",
    "train_X, train_Y = np.array(train_X), np.array(train_Y)\n",
    "test_X, test_Y = np.array(test_X), np.array(test_Y)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3938acdb",
   "metadata": {},
   "source": [
    "下面代码展示朴素贝叶斯的训练和预测。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "f13251b7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "P(计算机类) = 0.4453460067230787\n",
      "P(艺术传媒类) = 0.26660484525327466\n",
      "P(经管类) = 0.2880491480236467\n",
      "P(教程|计算机类) = 0.5726495726495726\n",
      "P(基础|计算机类) = 0.6503006012024048\n",
      "P(设计|计算机类) = 0.606694560669456\n",
      "test example-0, prediction = 0, label = 0\n",
      "test example-1, prediction = 0, label = 0\n",
      "test example-2, prediction = 1, label = 1\n",
      "test example-3, prediction = 1, label = 1\n",
      "test example-4, prediction = 1, label = 1\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "class NaiveBayes:\n",
    "    def __init__(self, num_classes, vocab_size):\n",
    "        self.num_classes = num_classes\n",
    "        self.vocab_size = vocab_size\n",
    "        self.prior = np.zeros(num_classes, dtype=np.float64)\n",
    "        self.likelihood = np.zeros((num_classes, vocab_size),\\\n",
    "            dtype=np.float64)\n",
    "        \n",
    "    def fit(self, X, Y):\n",
    "        # NaiveBayes的训练主要涉及先验概率和似然的估计，\n",
    "        # 这两者都可以通过计数简单获得\n",
    "        for x, y in zip(X, Y):\n",
    "            self.prior[y] += 1\n",
    "            for token_id in x:\n",
    "                self.likelihood[y, token_id] += 1\n",
    "                \n",
    "        self.prior /= self.prior.sum()\n",
    "        # laplace平滑\n",
    "        self.likelihood += 1\n",
    "        self.likelihood /= self.likelihood.sum(axis=0)\n",
    "        # 为了避免精度溢出，使用对数概率\n",
    "        self.prior = np.log(self.prior)\n",
    "        self.likelihood = np.log(self.likelihood)\n",
    "    \n",
    "    def predict(self, X):\n",
    "        # 算出各个类别的先验概率与似然的乘积，找出最大的作为分类结果\n",
    "        preds = []\n",
    "        for x in X:\n",
    "            p = np.zeros(self.num_classes, dtype=np.float64)\n",
    "            for i in range(self.num_classes):\n",
    "                p[i] += self.prior[i]\n",
    "                for token in x:\n",
    "                    p[i] += self.likelihood[i, token]\n",
    "            preds.append(np.argmax(p))\n",
    "        return preds\n",
    "\n",
    "nb = NaiveBayes(len(dataset.label2id), len(dataset.token2id))\n",
    "train_X, train_Y = [], []\n",
    "for data in dataset.train_data:\n",
    "    train_X.append(data['token_ids'])\n",
    "    train_Y.append(data['label'])\n",
    "nb.fit(train_X, train_Y)\n",
    "\n",
    "for i in range(3):\n",
    "    print(f'P({dataset.id2label[i]}) = {np.exp(nb.prior[i])}')\n",
    "for i in range(3):\n",
    "    print(f'P({dataset.id2token[i]}|{dataset.id2label[0]}) = '+\\\n",
    "          f'{np.exp(nb.likelihood[0, i])}')\n",
    "\n",
    "test_X, test_Y = [], []\n",
    "for data in dataset.test_data:\n",
    "    test_X.append(data['token_ids'])\n",
    "    test_Y.append(data['label'])\n",
    "    \n",
    "NB_preds = nb.predict(test_X)\n",
    "    \n",
    "for i, (p, y) in enumerate(zip(NB_preds, test_Y)):\n",
    "    if i >= 5:\n",
    "        break\n",
    "    print(f'test example-{i}, prediction = {p}, label = {y}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1cf6399",
   "metadata": {},
   "source": [
    "下面使用第3章介绍的TF-IDF方法得到文档的特征向量，并使用PyTorch实现逻辑斯谛回归模型的训练和预测。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "8679bd59-83bf-4281-8847-9fa3335eb4d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "D:\\Promgfile\\Anaconda\\envs\\pytorch\\lib\\site-packages\\utils\\__init__.py\n"
     ]
    }
   ],
   "source": [
    "import utils\n",
    "print(utils.__file__)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "21a3bc79",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\Promgfile\\Anaconda\\envs\\pytorch\\lib\\site-packages\\spacy\\util.py:894: UserWarning: [W095] Model 'zh_core_web_sm' (3.8.0) was trained with spaCy v3.8.0 and may not be 100% compatible with the current version (4.0.0.dev2). If you see errors or degraded performance, download a newer compatible model or retrain your custom model with the current spaCy version. For more details and available updates, run: python -m spacy validate\n",
      "  warnings.warn(warn_msg)\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import sys\n",
    "sys.path.append('../code')\n",
    "from my_utils import TFIDF  # utils 和系统中的 utils 库重名了，如上代码显示，因此可以将自己编写的 utils 重命名为 my_utils\n",
    "        \n",
    "tfidf = TFIDF(len(dataset.token2id))\n",
    "tfidf.fit(train_X)\n",
    "train_F = tfidf.transform(train_X)\n",
    "test_F = tfidf.transform(test_X)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc8af30b",
   "metadata": {},
   "source": [
    "逻辑斯谛回归可以看作一个一层的神经网络模型，使用PyTorch实现可以方便地利用自动求导功能。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1ddebf0c",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "epoch:   0%|                         | 0/50 [00:00<?, ?it/s]C:\\Users\\WuMing\\AppData\\Local\\Temp\\ipykernel_23108\\1080208690.py:81: UserWarning: Converting a tensor with requires_grad=True to a scalar may lead to unexpected behavior.\n",
      "Consider using tensor.detach() first. (Triggered internally at C:\\actions-runner\\_work\\pytorch\\pytorch\\pytorch\\aten\\src\\ATen\\native\\Scalar.cpp:23.)\n",
      "  pbar.set_description(f'epoch-{epoch}, loss={loss.item():.4f}')\n",
      "epoch-49, loss=0.2193: 100%|█| 50/50 [00:04<00:00, 11.31it/s\n"
     ]
    },
    {
     "data": {
      "image/png": "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A1rnHThwAO3atQMAtGvXDqmpqUhNTVUfP3v2LLKzs9G+fXsAFbU6cXFxtX6fRGRZrPkhIrXw8HAcPHgQycnJ8Pb2RkBAgMGyrVq1wm+//YaRI0dCJpNh3rx5ktRY/Otf/8LixYvRsmVLtG3bFsuXL8edO3cgk8kMnjN79mz07t0bM2bMwJQpU1CvXj2cPXsWW7duxaeffqout2/fPrz//vsYPXo0tm7dil9++QUbNmwAAMTExKBTp04YP348PvroI5SXl+O5557DwIED1U2ECxYswJAhQ9CiRQs8/vjjKC8vx99//43Zs2db96EQkVGs+SEitVmzZkGhUKB9+/YICgoy2n9n2bJlqF+/Pvr06YORI0ciNjYW3bp1s2G0FWbPno1x48Zh4sSJiI6OVvff8fDwMHhO586dsWvXLly8eBH9+/dH165dMX/+fDRq1Eir3Msvv4wjR46ga9eueOedd7Bs2TLExsYCqGie+v3331G/fn0MGDAAMTExiIiIwNq1a9XnDxo0CL/88gv++OMPdOnSBffddx8OHTpknQdBRCaTido2nhMR2RGVSoV27drhsccew9tvv13j64SHh+PFF1/Eiy++aLngiMgusNmLiBza1atXsWXLFgwcOBAlJSX49NNPkZSUhCeeeELq0IjITrHZi4gcmlwux+rVq9GjRw/07dsXp06dwrZt29Qdk4mIqmKzFxERETkV1vwQERGRU2HyQ0RERE6FyQ8RERE5FSY/RERE5FSY/BAREZFTYfJDREREToXJDxERETkVJj9ERETkVP4fnTiBa7TxnQsAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "eval_loss = 0.2869\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "\n",
    "class LR(nn.Module):\n",
    "    def __init__(self, input_dim, output_dim):\n",
    "        super(LR, self).__init__()\n",
    "        self.linear = nn.Linear(input_dim, output_dim)\n",
    "        \n",
    "    def forward(self, input_feats, labels=None):\n",
    "        outputs = self.linear(input_feats)\n",
    "        \n",
    "        if labels is not None:\n",
    "            loss_fc = nn.CrossEntropyLoss()\n",
    "            loss = loss_fc(outputs, labels)\n",
    "            return (loss, outputs)\n",
    "        \n",
    "        return outputs\n",
    "\n",
    "model = LR(len(dataset.token2id), len(dataset.label2id))\n",
    "\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torch.optim import SGD, Adam\n",
    "\n",
    "# 使用PyTorch的DataLoader来进行数据循环，因此按照PyTorch的接口\n",
    "# 实现myDataset和DataCollator两个类\n",
    "# myDataset是对特征向量和标签的简单封装便于对齐接口，\n",
    "# DataCollator用于批量将数据转化为PyTorch支持的张量类型\n",
    "class myDataset(Dataset):\n",
    "    def __init__(self, X, Y):\n",
    "        self.X = X\n",
    "        self.Y = Y\n",
    "        \n",
    "    def __len__(self):\n",
    "        return len(self.X)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        return (self.X[idx], self.Y[idx])\n",
    "\n",
    "class DataCollator:\n",
    "    @classmethod\n",
    "    def collate_batch(cls, batch):\n",
    "        feats, labels = [], []\n",
    "        for x, y in batch:\n",
    "            feats.append(x)\n",
    "            labels.append(y)\n",
    "        # 直接将一个ndarray的列表转化为张量是非常慢的，\n",
    "        # 所以需要提前将列表转化为一整个ndarray\n",
    "        feats = torch.tensor(np.array(feats), dtype=torch.float)\n",
    "        labels = torch.tensor(np.array(labels), dtype=torch.long)\n",
    "        return {'input_feats': feats, 'labels': labels}\n",
    "\n",
    "# 设置训练超参数和优化器，模型初始化\n",
    "epochs = 50\n",
    "batch_size = 128\n",
    "learning_rate = 1e-3\n",
    "weight_decay = 0\n",
    "\n",
    "train_dataset = myDataset(train_F, train_Y)\n",
    "test_dataset = myDataset(test_F, test_Y)\n",
    "\n",
    "data_collator = DataCollator()\n",
    "train_dataloader = DataLoader(train_dataset, batch_size=batch_size,\\\n",
    "    shuffle=True, collate_fn=data_collator.collate_batch)\n",
    "test_dataloader = DataLoader(test_dataset, batch_size=batch_size,\\\n",
    "    shuffle=False, collate_fn=data_collator.collate_batch)\n",
    "optimizer = Adam(model.parameters(), lr=learning_rate,\\\n",
    "    weight_decay=weight_decay)\n",
    "model.zero_grad()\n",
    "model.train()\n",
    "\n",
    "from tqdm import tqdm, trange\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 模型训练\n",
    "with trange(epochs, desc='epoch', ncols=60) as pbar:\n",
    "    epoch_loss = []\n",
    "    for epoch in pbar:\n",
    "        model.train()\n",
    "        for step, batch in enumerate(train_dataloader):\n",
    "            loss = model(**batch)[0]\n",
    "            pbar.set_description(f'epoch-{epoch}, loss={loss.item():.4f}')\n",
    "            loss.backward()\n",
    "            optimizer.step()\n",
    "            model.zero_grad()\n",
    "            epoch_loss.append(loss.item())\n",
    "\n",
    "    epoch_loss = np.array(epoch_loss)\n",
    "    # 打印损失曲线\n",
    "    plt.plot(range(len(epoch_loss)), epoch_loss)\n",
    "    plt.xlabel('training epoch')\n",
    "    plt.ylabel('loss')\n",
    "    plt.show()\n",
    "    \n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        loss_terms = []\n",
    "        for batch in test_dataloader:\n",
    "            loss = model(**batch)[0]\n",
    "            loss_terms.append(loss.item())\n",
    "        print(f'eval_loss = {np.mean(loss_terms):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10808854",
   "metadata": {},
   "source": [
    "下面的代码使用训练好的模型对测试集进行预测，并报告分类结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "11a9bf62",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "test example-0, prediction = 0, label = 0\n",
      "test example-1, prediction = 0, label = 0\n",
      "test example-2, prediction = 1, label = 1\n",
      "test example-3, prediction = 1, label = 1\n",
      "test example-4, prediction = 1, label = 1\n"
     ]
    }
   ],
   "source": [
    "LR_preds = []\n",
    "model.eval()\n",
    "for batch in test_dataloader:\n",
    "    with torch.no_grad():\n",
    "        _, preds = model(**batch)\n",
    "        preds = np.argmax(preds, axis=1)\n",
    "        LR_preds.extend(preds)\n",
    "            \n",
    "for i, (p, y) in enumerate(zip(LR_preds, test_Y)):\n",
    "    if i >= 5:\n",
    "        break\n",
    "    print(f'test example-{i}, prediction = {p}, label = {y}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5feb65e",
   "metadata": {},
   "source": [
    "下面的代码展示多分类情况下宏平均和微平均的算法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "a5ac32c5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NB: micro-f1 = 0.8961520630505331, macro-f1 = 0.8948572078813896\n",
      "LR: micro-f1 = 0.9142327306444136, macro-f1 = 0.913473572128313\n"
     ]
    }
   ],
   "source": [
    "test_Y = np.array(test_Y)\n",
    "NB_preds = np.array(NB_preds)\n",
    "LR_preds = np.array(LR_preds)\n",
    "\n",
    "def micro_f1(preds, labels):\n",
    "    TP = np.sum(preds == labels)\n",
    "    FN = FP = 0\n",
    "    for i in range(len(dataset.label2id)):\n",
    "        FN += np.sum((preds == i) & (labels != i))\n",
    "        FP += np.sum((preds != i) & (labels == i))\n",
    "    precision = TP / (TP + FP)\n",
    "    recall = TP / (TP + FN)\n",
    "    f1 = 2 * precision * recall / (precision + recall)\n",
    "    return f1\n",
    "\n",
    "def macro_f1(preds, labels):\n",
    "    f_scores = []\n",
    "    for i in range(len(dataset.label2id)):\n",
    "        TP = np.sum((preds == i) & (labels == i))\n",
    "        FN = np.sum((preds == i) & (labels != i))\n",
    "        FP = np.sum((preds != i) & (labels == i))\n",
    "        precision = TP / (TP + FP)\n",
    "        recall = TP / (TP + FN)\n",
    "        f1 = 2 * precision * recall / (precision + recall)\n",
    "        f_scores.append(f1)\n",
    "    return np.mean(f_scores)\n",
    "\n",
    "print(f'NB: micro-f1 = {micro_f1(NB_preds, test_Y)}, '+\\\n",
    "      f'macro-f1 = {macro_f1(NB_preds, test_Y)}')\n",
    "print(f'LR: micro-f1 = {micro_f1(LR_preds, test_Y)}, '+\\\n",
    "      f'macro-f1 = {macro_f1(LR_preds, test_Y)}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "937fdc5c-3b90-455a-91f4-1202fbf514dc",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Processing d:\\schoolfile\\社会网路舆情演化分析\\zh_core_web_sm-3.8.0-py3-none-any.whl\n",
      "Requirement already satisfied: spacy-pkuseg<2.0.0,>=1.0.0 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from zh-core-web-sm==3.8.0) (1.0.0)\n",
      "Requirement already satisfied: srsly<3.0.0,>=2.3.0 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from spacy-pkuseg<2.0.0,>=1.0.0->zh-core-web-sm==3.8.0) (2.5.1)\n",
      "Requirement already satisfied: numpy<3.0.0,>=2.0.0 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from spacy-pkuseg<2.0.0,>=1.0.0->zh-core-web-sm==3.8.0) (2.0.1)\n",
      "Requirement already satisfied: catalogue<2.1.0,>=2.0.3 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from srsly<3.0.0,>=2.3.0->spacy-pkuseg<2.0.0,>=1.0.0->zh-core-web-sm==3.8.0) (2.0.10)\n",
      "zh-core-web-sm is already installed with the same version as the provided wheel. Use --force-reinstall to force an installation of the wheel.\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "pip install zh_core_web_sm-3.8.0-py3-none-any.whl"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a94f0a88",
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20fd56d8",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0114aa7",
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "85675a12",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Processing d:\\schoolfile\\社会网路舆情演化分析\\spacy-4.0.0.dev2-cp39-cp39-win_amd64.whlNote: you may need to restart the kernel to use updated packages.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  WARNING: The script weasel.exe is installed in 'D:\\Promgfile\\Anaconda\\envs\\pytorch\\Scripts' which is not on PATH.\n",
      "  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\n",
      "  WARNING: The script spacy.exe is installed in 'D:\\Promgfile\\Anaconda\\envs\\pytorch\\Scripts' which is not on PATH.\n",
      "  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.\n",
      "ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n",
      "spacy-pkuseg 1.0.0 requires numpy<3.0.0,>=2.0.0; python_version >= \"3.9\", but you have numpy 1.26.4 which is incompatible.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Collecting spacy-legacy<4.1.0,>=4.0.0.dev0 (from spacy==4.0.0.dev2)\n",
      "  Downloading spacy_legacy-4.0.0.dev1-py2.py3-none-any.whl.metadata (3.1 kB)\n",
      "Collecting spacy-loggers<2.0.0,>=1.0.0 (from spacy==4.0.0.dev2)\n",
      "  Using cached spacy_loggers-1.0.5-py3-none-any.whl.metadata (23 kB)\n",
      "Collecting murmurhash<1.1.0,>=0.28.0 (from spacy==4.0.0.dev2)\n",
      "  Using cached murmurhash-1.0.12-cp39-cp39-win_amd64.whl.metadata (2.2 kB)\n",
      "Collecting cymem<2.1.0,>=2.0.2 (from spacy==4.0.0.dev2)\n",
      "  Using cached cymem-2.0.11-cp39-cp39-win_amd64.whl.metadata (8.8 kB)\n",
      "Collecting preshed<3.1.0,>=3.0.2 (from spacy==4.0.0.dev2)\n",
      "  Using cached preshed-3.0.9-cp39-cp39-win_amd64.whl.metadata (2.2 kB)\n",
      "Collecting thinc<9.1.0,>=9.0.0.dev4 (from spacy==4.0.0.dev2)\n",
      "  Downloading thinc-9.0.0-cp39-cp39-win_amd64.whl.metadata (15 kB)\n",
      "Collecting wasabi<1.2.0,>=0.9.1 (from spacy==4.0.0.dev2)\n",
      "  Using cached wasabi-1.1.3-py3-none-any.whl.metadata (28 kB)\n",
      "Collecting srsly<3.0.0,>=2.4.3 (from spacy==4.0.0.dev2)\n",
      "  Using cached srsly-2.5.1-cp39-cp39-win_amd64.whl.metadata (20 kB)\n",
      "Collecting catalogue<2.1.0,>=2.0.6 (from spacy==4.0.0.dev2)\n",
      "  Using cached catalogue-2.0.10-py3-none-any.whl.metadata (14 kB)\n",
      "Collecting weasel<0.4.0,>=0.1.0 (from spacy==4.0.0.dev2)\n",
      "  Downloading weasel-0.3.4-py3-none-any.whl.metadata (4.7 kB)\n",
      "Collecting typer<0.10.0,>=0.3.0 (from spacy==4.0.0.dev2)\n",
      "  Downloading typer-0.9.4-py3-none-any.whl.metadata (14 kB)\n",
      "Collecting smart-open<7.0.0,>=5.2.1 (from spacy==4.0.0.dev2)\n",
      "  Downloading smart_open-6.4.0-py3-none-any.whl.metadata (21 kB)\n",
      "Requirement already satisfied: tqdm<5.0.0,>=4.38.0 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from spacy==4.0.0.dev2) (4.67.1)\n",
      "Requirement already satisfied: requests<3.0.0,>=2.13.0 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from spacy==4.0.0.dev2) (2.32.3)\n",
      "Collecting pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4 (from spacy==4.0.0.dev2)\n",
      "  Using cached pydantic-2.11.4-py3-none-any.whl.metadata (66 kB)\n",
      "Requirement already satisfied: jinja2 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from spacy==4.0.0.dev2) (3.1.4)\n",
      "Requirement already satisfied: setuptools in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from spacy==4.0.0.dev2) (78.1.1)\n",
      "Requirement already satisfied: packaging>=20.0 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from spacy==4.0.0.dev2) (24.2)\n",
      "Collecting langcodes<4.0.0,>=3.2.0 (from spacy==4.0.0.dev2)\n",
      "  Using cached langcodes-3.5.0-py3-none-any.whl.metadata (29 kB)\n",
      "Requirement already satisfied: numpy>=1.19.0 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from spacy==4.0.0.dev2) (1.26.4)\n",
      "Collecting language-data>=1.2 (from langcodes<4.0.0,>=3.2.0->spacy==4.0.0.dev2)\n",
      "  Using cached language_data-1.3.0-py3-none-any.whl.metadata (4.3 kB)\n",
      "Collecting annotated-types>=0.6.0 (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy==4.0.0.dev2)\n",
      "  Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)\n",
      "Collecting pydantic-core==2.33.2 (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy==4.0.0.dev2)\n",
      "  Using cached pydantic_core-2.33.2-cp39-cp39-win_amd64.whl.metadata (6.9 kB)\n",
      "Requirement already satisfied: typing-extensions>=4.12.2 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy==4.0.0.dev2) (4.12.2)\n",
      "Collecting typing-inspection>=0.4.0 (from pydantic!=1.8,!=1.8.1,<3.0.0,>=1.7.4->spacy==4.0.0.dev2)\n",
      "  Using cached typing_inspection-0.4.0-py3-none-any.whl.metadata (2.6 kB)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from requests<3.0.0,>=2.13.0->spacy==4.0.0.dev2) (3.4.2)\n",
      "Requirement already satisfied: idna<4,>=2.5 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from requests<3.0.0,>=2.13.0->spacy==4.0.0.dev2) (3.10)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from requests<3.0.0,>=2.13.0->spacy==4.0.0.dev2) (2.4.0)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from requests<3.0.0,>=2.13.0->spacy==4.0.0.dev2) (2025.4.26)\n",
      "Collecting blis<0.8.0,>=0.7.8 (from thinc<9.1.0,>=9.0.0.dev4->spacy==4.0.0.dev2)\n",
      "  Downloading blis-0.7.11-cp39-cp39-win_amd64.whl.metadata (7.6 kB)\n",
      "Collecting confection<1.0.0,>=0.0.1 (from thinc<9.1.0,>=9.0.0.dev4->spacy==4.0.0.dev2)\n",
      "  Using cached confection-0.1.5-py3-none-any.whl.metadata (19 kB)\n",
      "Requirement already satisfied: colorama in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from tqdm<5.0.0,>=4.38.0->spacy==4.0.0.dev2) (0.4.6)\n",
      "Collecting click<9.0.0,>=7.1.1 (from typer<0.10.0,>=0.3.0->spacy==4.0.0.dev2)\n",
      "  Using cached click-8.1.8-py3-none-any.whl.metadata (2.3 kB)\n",
      "Collecting cloudpathlib<0.17.0,>=0.7.0 (from weasel<0.4.0,>=0.1.0->spacy==4.0.0.dev2)\n",
      "  Downloading cloudpathlib-0.16.0-py3-none-any.whl.metadata (14 kB)\n",
      "Collecting marisa-trie>=1.1.0 (from language-data>=1.2->langcodes<4.0.0,>=3.2.0->spacy==4.0.0.dev2)\n",
      "  Using cached marisa_trie-1.2.1-cp39-cp39-win_amd64.whl.metadata (9.3 kB)\n",
      "Requirement already satisfied: MarkupSafe>=2.0 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from jinja2->spacy==4.0.0.dev2) (2.1.5)\n",
      "Using cached catalogue-2.0.10-py3-none-any.whl (17 kB)\n",
      "Using cached cymem-2.0.11-cp39-cp39-win_amd64.whl (39 kB)\n",
      "Using cached langcodes-3.5.0-py3-none-any.whl (182 kB)\n",
      "Using cached murmurhash-1.0.12-cp39-cp39-win_amd64.whl (25 kB)\n",
      "Using cached preshed-3.0.9-cp39-cp39-win_amd64.whl (122 kB)\n",
      "Using cached pydantic-2.11.4-py3-none-any.whl (443 kB)\n",
      "Using cached pydantic_core-2.33.2-cp39-cp39-win_amd64.whl (2.0 MB)\n",
      "Downloading smart_open-6.4.0-py3-none-any.whl (57 kB)\n",
      "Downloading spacy_legacy-4.0.0.dev1-py2.py3-none-any.whl (30 kB)\n",
      "Using cached spacy_loggers-1.0.5-py3-none-any.whl (22 kB)\n",
      "Using cached srsly-2.5.1-cp39-cp39-win_amd64.whl (633 kB)\n",
      "Downloading thinc-9.0.0-cp39-cp39-win_amd64.whl (1.3 MB)\n",
      "   ---------------------------------------- 0.0/1.3 MB ? eta -:--:--\n",
      "   ---------------------------------------- 0.0/1.3 MB ? eta -:--:--\n",
      "   -------- ------------------------------- 0.3/1.3 MB ? eta -:--:--\n",
      "   ---------------- ----------------------- 0.5/1.3 MB 2.1 MB/s eta 0:00:01\n",
      "   -------------------------------- ------- 1.0/1.3 MB 2.2 MB/s eta 0:00:01\n",
      "   ---------------------------------------- 1.3/1.3 MB 2.0 MB/s eta 0:00:00\n",
      "Downloading blis-0.7.11-cp39-cp39-win_amd64.whl (6.6 MB)\n",
      "   ---------------------------------------- 0.0/6.6 MB ? eta -:--:--\n",
      "   - -------------------------------------- 0.3/6.6 MB ? eta -:--:--\n",
      "   ---- ----------------------------------- 0.8/6.6 MB 2.1 MB/s eta 0:00:03\n",
      "   ------ --------------------------------- 1.0/6.6 MB 1.8 MB/s eta 0:00:04\n",
      "   --------- ------------------------------ 1.6/6.6 MB 2.0 MB/s eta 0:00:03\n",
      "   ------------ --------------------------- 2.1/6.6 MB 2.3 MB/s eta 0:00:03\n",
      "   --------------- ------------------------ 2.6/6.6 MB 2.3 MB/s eta 0:00:02\n",
      "   ------------------ --------------------- 3.1/6.6 MB 2.3 MB/s eta 0:00:02\n",
      "   ----------------------- ---------------- 3.9/6.6 MB 2.5 MB/s eta 0:00:02\n",
      "   ------------------------- -------------- 4.2/6.6 MB 2.5 MB/s eta 0:00:01\n",
      "   ------------------------------ --------- 5.0/6.6 MB 2.6 MB/s eta 0:00:01\n",
      "   --------------------------------- ------ 5.5/6.6 MB 2.5 MB/s eta 0:00:01\n",
      "   ------------------------------------ --- 6.0/6.6 MB 2.6 MB/s eta 0:00:01\n",
      "   ---------------------------------------- 6.6/6.6 MB 2.6 MB/s eta 0:00:00\n",
      "Using cached confection-0.1.5-py3-none-any.whl (35 kB)\n",
      "Downloading typer-0.9.4-py3-none-any.whl (45 kB)\n",
      "Using cached click-8.1.8-py3-none-any.whl (98 kB)\n",
      "Using cached wasabi-1.1.3-py3-none-any.whl (27 kB)\n",
      "Downloading weasel-0.3.4-py3-none-any.whl (50 kB)\n",
      "Downloading cloudpathlib-0.16.0-py3-none-any.whl (45 kB)\n",
      "Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB)\n",
      "Using cached language_data-1.3.0-py3-none-any.whl (5.4 MB)\n",
      "Using cached marisa_trie-1.2.1-cp39-cp39-win_amd64.whl (152 kB)\n",
      "Using cached typing_inspection-0.4.0-py3-none-any.whl (14 kB)\n",
      "Installing collected packages: cymem, wasabi, typing-inspection, spacy-loggers, smart-open, pydantic-core, murmurhash, marisa-trie, cloudpathlib, click, catalogue, blis, annotated-types, typer, srsly, spacy-legacy, pydantic, preshed, language-data, langcodes, confection, weasel, thinc, spacy\n",
      "\n",
      "   --- ------------------------------------  2/24 [typing-inspection]\n",
      "  Attempting uninstall: smart-open\n",
      "   --- ------------------------------------  2/24 [typing-inspection]\n",
      "    Found existing installation: smart-open 7.1.0\n",
      "   --- ------------------------------------  2/24 [typing-inspection]\n",
      "    Uninstalling smart-open-7.1.0:\n",
      "   --- ------------------------------------  2/24 [typing-inspection]\n",
      "      Successfully uninstalled smart-open-7.1.0\n",
      "   --- ------------------------------------  2/24 [typing-inspection]\n",
      "   ------ ---------------------------------  4/24 [smart-open]\n",
      "   ---------- -----------------------------  6/24 [murmurhash]\n",
      "   ------------- --------------------------  8/24 [cloudpathlib]\n",
      "   ---------------- ----------------------- 10/24 [catalogue]\n",
      "   -------------------- ------------------- 12/24 [annotated-types]\n",
      "   ----------------------- ---------------- 14/24 [srsly]\n",
      "   ----------------------- ---------------- 14/24 [srsly]\n",
      "   ----------------------- ---------------- 14/24 [srsly]\n",
      "   ----------------------- ---------------- 14/24 [srsly]\n",
      "   ----------------------- ---------------- 14/24 [srsly]\n",
      "   ------------------------- -------------- 15/24 [spacy-legacy]\n",
      "   -------------------------- ------------- 16/24 [pydantic]\n",
      "   -------------------------- ------------- 16/24 [pydantic]\n",
      "   -------------------------- ------------- 16/24 [pydantic]\n",
      "   -------------------------- ------------- 16/24 [pydantic]\n",
      "   -------------------------- ------------- 16/24 [pydantic]\n",
      "   ---------------------------- ----------- 17/24 [preshed]\n",
      "   ------------------------------ --------- 18/24 [language-data]\n",
      "   ------------------------------ --------- 18/24 [language-data]\n",
      "   ------------------------------ --------- 18/24 [language-data]\n",
      "   ------------------------------- -------- 19/24 [langcodes]\n",
      "   ----------------------------------- ---- 21/24 [weasel]\n",
      "   ------------------------------------ --- 22/24 [thinc]\n",
      "   ------------------------------------ --- 22/24 [thinc]\n",
      "   ------------------------------------ --- 22/24 [thinc]\n",
      "   ------------------------------------ --- 22/24 [thinc]\n",
      "   ------------------------------------ --- 22/24 [thinc]\n",
      "   ------------------------------------ --- 22/24 [thinc]\n",
      "   ------------------------------------ --- 22/24 [thinc]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   -------------------------------------- - 23/24 [spacy]\n",
      "   ---------------------------------------- 24/24 [spacy]\n",
      "\n",
      "Successfully installed annotated-types-0.7.0 blis-0.7.11 catalogue-2.0.10 click-8.1.8 cloudpathlib-0.16.0 confection-0.1.5 cymem-2.0.11 langcodes-3.5.0 language-data-1.3.0 marisa-trie-1.2.1 murmurhash-1.0.12 preshed-3.0.9 pydantic-2.11.4 pydantic-core-2.33.2 smart-open-6.4.0 spacy-4.0.0.dev2 spacy-legacy-4.0.0.dev1 spacy-loggers-1.0.5 srsly-2.5.1 thinc-9.0.0 typer-0.9.4 typing-inspection-0.4.0 wasabi-1.1.3 weasel-0.3.4\n"
     ]
    }
   ],
   "source": [
    "   pip install spacy-4.0.0.dev2-cp39-cp39-win_amd64.whl"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "c1d394eb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Processing d:\\schoolfile\\社会网路舆情演化分析\\blis-1.2.0-cp39-cp39-win_amd64.whl\n",
      "Requirement already satisfied: numpy<3.0.0,>=1.19.0 in d:\\promgfile\\anaconda\\envs\\pytorch\\lib\\site-packages (from blis==1.2.0) (1.26.4)\n",
      "Installing collected packages: blis\n",
      "  Attempting uninstall: blis\n",
      "    Found existing installation: blis 0.7.11\n",
      "    Uninstalling blis-0.7.11:\n",
      "      Successfully uninstalled blis-0.7.11\n",
      "Successfully installed blis-1.2.0\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n",
      "thinc 9.0.0 requires blis<0.8.0,>=0.7.8, but you have blis 1.2.0 which is incompatible.\n"
     ]
    }
   ],
   "source": [
    "pip install blis-1.2.0-cp39-cp39-win_amd64.whl"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "8e044a74",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting utils\n",
      "  Downloading utils-1.0.2.tar.gz (13 kB)\n",
      "  Preparing metadata (setup.py): started\n",
      "  Preparing metadata (setup.py): finished with status 'done'\n",
      "Building wheels for collected packages: utils\n",
      "  Building wheel for utils (setup.py): started\n",
      "  Building wheel for utils (setup.py): finished with status 'done'\n",
      "  Created wheel for utils: filename=utils-1.0.2-py2.py3-none-any.whl size=14011 sha256=0ff00ec998b594483efdbe09cd24144e0f50c0bbb01e72bb8216ba52aa03971a\n",
      "  Stored in directory: c:\\users\\wuming\\appdata\\local\\pip\\cache\\wheels\\4c\\a5\\a3\\ab48e06c936b39960801612ee2767ff53764119f33d3d646e7\n",
      "Successfully built utils\n",
      "Installing collected packages: utils\n",
      "Successfully installed utils-1.0.2\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  DEPRECATION: Building 'utils' using the legacy setup.py bdist_wheel mechanism, which will be removed in a future version. pip 25.3 will enforce this behaviour change. A possible replacement is to use the standardized build interface by setting the `--use-pep517` option, (possibly combined with `--no-build-isolation`), or adding a `pyproject.toml` file to the source tree of 'utils'. Discussion can be found at https://github.com/pypa/pip/issues/6334\n"
     ]
    }
   ],
   "source": [
    "pip install utils"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c220a2a3",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c9dc0281",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "env_name"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.21"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
